/*
 * This code implements a simple shell program
 * It supports the internal shell command "exit", 
 * backgrounding processes with "&", input redirection
 * with "<" and output redirection with ">".
 * However, this is not complete.
 */

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <termios.h>
#include <string.h>

// Method prototypes
extern char **getline();
void tilde (char **);
void add_to_bkg (pid_t);
void rm_from_bkg (pid_t);
int num_pipes (char **);
int pipe_execvp (char **, int);
int recursive_execvp_1 (char **, int);
int recursive_execvp_2 (char **, char **, int);
char ** last_command (char **);
char ** head (char **);

// Buffer size for reading in from pipe
#define BUF_SIZE 80

// The most amount of backgrounded processes we'll allow
// For the purpose of this assignment, we'll pretend we'll never exceed this amount
#define MAX_BKG 50
pid_t bkg_pid [MAX_BKG];

/*
 * Handle signals 
 */
void sig_handler(int signal) {
  // Block signals so that we can do some work in the handler
  sigset_t sigs;
  sigemptyset (& sigs);
  sigaddset (& sigs, SIGCHLD);
  sigaddset (& sigs, SIGALRM);
  sigaddset (& sigs, SIGINT);
  sigaddset (& sigs, SIGTERM);
  sigaddset (& sigs, SIGCONT);
  sigprocmask (SIG_BLOCK, & sigs, NULL);

  // Regardless of whether we get in here by SIGCHLD or SIGALRM, check to see if any children have finished
  int status, result, i;

  // Now check for any background children
  for (i = 0; i < MAX_BKG && bkg_pid [i] != 0; i++) {
    result = waitpid (bkg_pid [i], & status, WNOHANG);

    if (result > 0) {
      printf ("Child process returned %d\n", result);
      rm_from_bkg (result);
    }
  }

  if (signal == SIGINT) {
    int i;
    printf ("Signal caught -- SIGINT, killing all subprocesses\n");

    while (i < MAX_BKG && bkg_pid [i] != 0) {
      kill (bkg_pid [i], SIGTERM);
      i++;
    }

  } else if (signal == SIGALRM) {
    alarm (2);
  } else if (signal == SIGCONT) {
    printf ("Signal caught -- SIGCONT in process %d\n", getpgid());
  } else if (signal == SIGUSR1) {
    int status;
    int result;

    printf ("Signal caught -- SIGUSR1\n");
    //result = waitpid(bkg_pid, & status, 0);

    raise (SIGTSTP);
  }

  // Start listening to signals again
  sigprocmask (SIG_UNBLOCK, & sigs, NULL); }

/*
 * The main shell function
 */ 
main() {
  int i;
  char **args; 
  int result;
  int block;
  int output;
  int input;
  int pipes;
  char *output_filename;
  char *input_filename;
  
  // Initialize bkg_pid
  for (i = 0; i < MAX_BKG; i++) {
    bkg_pid [i] = 0;
  }

  // Set up the signal handler
  sigset (SIGCHLD, sig_handler);
  sigset (SIGALRM, sig_handler); // allows us to continuously monitor children
  sigset (SIGINT, sig_handler);
  sigset (SIGTERM, sig_handler);
  sigset (SIGCONT, sig_handler);
  sigset (SIGUSR1, sig_handler);

  alarm (2); // to constantly check to see if processes are finishing while we're at the prompt

  // This will allow us to run a process in the background and not have the flex scanner fail on us
  sigset_t ss;
  sigemptyset (& ss);
  sigaddset (& ss, SIGCHLD);
  sigaddset (& ss, SIGALRM);
  
  // Loop forever
  while(1) {
    // Block any signals until after we get the input
    sigprocmask (SIG_BLOCK, & ss, NULL);

    // Print out the prompt and get the input
    printf("--> ");
    args = getline();

    // Start listening to signals again
    sigprocmask (SIG_UNBLOCK, & ss, NULL);

    // No input, continue
    if(args[0] == NULL)
      continue;

    // Check for internal shell commands, such as exit
    if(internal_command(args))
      continue;

    // Check to see if user referenced home with ~
    tilde(args);

    // Check for an ampersand
    block = (ampersand(args) == 0);

    // Check for redirected input
    input = redirect_input(args, &input_filename);
    if (input == -1) {
	printf ("no input file listed, exiting...\n");
	exit (-1);
    }

    switch(input) {
    case -1:
      printf("Syntax error!\n");
      continue;
      break;
    case 0:
      break;
    case 1:
      printf("Redirecting input from: %s\n", input_filename);
      break;
    }

    // Check for redirected output
    output = redirect_output(args, &output_filename);

    switch(output) {
    case -1:
      printf("Syntax error!\n");
      continue;
      break;
    case 0:
      break;
    case 1:
      printf("Redirecting output to: %s\n", output_filename);
      break;
    case 2:
      printf("Appending output to: %s\n", output_filename);
      break;
    }

    // Do the command
    do_command(args, block, pipes,
	       input, input_filename, 
	       output, output_filename);
  }
}

/*
 * Check for ampersand as the last argument
 */
int ampersand(char **args) {
  int i;

  for(i = 1; args[i] != NULL; i++) ;

  if(args[i-1][0] == '&') {
    free(args[i-1]);
    args[i-1] = NULL;
    return 1;
  } else {
    return 0;
  }
  
  return 0;
}

/* 
 * Check for internal commands
 * Returns true if there is more to do, false otherwise 
 */
int internal_command(char **args) {
  int result = 0;

  if(strcmp(args[0], "exit") == 0) {
    exit(0);
  } else if (strcmp(args[0], "fg") == 0) {
    printf ("fg called\n");

    if (bkg_pid [0] == 0) {
      printf ("No jobs to foreground\n");
    } else {
      raise (SIGUSR1);
//      pid_t stdin_pgid = tcgetpgrp (STDIN_FILENO);
//      pid_t stdout_pgid = tcgetpgrp (STDOUT_FILENO);

//     if (tcsetpgrp (stdin_pgid, bkg_pid [0]) == -1) perror ("tcsetpgrp -- in");
//      if (tcsetpgrp (stdout_pgid, bkg_pid [0]) == -1) perror ("tcsetpgrp -- out");
    }


    result = 1;
  } else if (strcmp (args[0], "jobs") == 0) {
    int i;

    printf ("\nPrinting jobs\n");
    printf ("-------------\n");

    for (i = 0; i < MAX_BKG && bkg_pid [i] != 0; i++)
      printf ("[%d] - %d\n", i + 1, bkg_pid [i]);

    printf ("\n");
    result = 1;
  } else if (strcmp (args [0], "kj") == 0) { // just for testing purposes
    raise (SIGINT);
  }


  return result;
}

/* 
 * Do the command
 */
int do_command(char **args, int block, int pipes,
	       int input, char *input_filename,
	       int output, char *output_filename) {
  
  int status;
  int result;
  pid_t child_id;
  pid_t parent_id = getpgrp();

  // Fork the child process
  child_id = fork();

  // Check for errors in fork()
  switch(child_id) {
  case EAGAIN:
    perror("Error EAGAIN: ");
    return;
  case ENOMEM:
    perror("Error ENOMEM: ");
    return;
  }

  if(child_id == 0) {

    // Have the backgrounded child be in its own process group
    if (!block) {
      if (setpgrp() == -1)
        perror ("setpgrp");
    }

    // Set up redirection in the child process
    if(input)
      freopen(input_filename, "r", stdin);

    if(output == 1) {
      //Write to output_filename
      freopen(output_filename, "w+", stdout);
    } else if(output == 2) {
      //Append to output_filename
      freopen(output_filename, "a", stdout);
    }

    // Execute the command
    //result = execvp(args[0], args);
    result = pipe_execvp (args, num_pipes (args));
    printf ("MYS [0] -- result = %d\n", result);

    if (result == -1) {
	printf ("Invalid use of command\n");
    }

    exit(-1);
  }

  // Wait for the child process to complete, if necessary
  if(block) {
    printf("Waiting for child, pid = %d\n", child_id);
    result = waitpid(child_id, &status, 0);
  } else {
    // If we're not going to wait for it, add it to the queue to keep track of it
    add_to_bkg (child_id);
  }
}

/*
 * Check for input redirection
 */
int redirect_input(char **args, char **input_filename) {
  int i;
  int j;

  for(i = 0; args[i] != NULL; i++) {

    // Look for the <
    if(args[i][0] == '<') {
      free(args[i]);

      // Read the filename
      if(args[i+1] != NULL) {
	*input_filename = args[i+1];
	printf ("input_filename is currently %s\n", *input_filename);

      } else {
	return -1;
      }

      // Adjust the rest of the arguments in the array
      for(j = i; args[j-1] != NULL; j++) {
	args[j] = args[j+2];
      }

      return 1;
    }
  }

  return 0;
}

/*
 * Check for output redirection
 */
int redirect_output(char **args, char **output_filename) {
  int i;
  int j;

  for(i = 0; args[i] != NULL; i++) {

    if(args[i][0] == '>' && args[i + 1][0] != 0 && args[i + 1][0] == '>') {
      free(args[i]);
      free(args[i + 1]);

      // Get the filename 
      if(args[i+2] != 0) {
	*output_filename = args[i+2];
      } else {
	return -1;
      }

      // Adjust the rest of the arguments in the array
      for(j = i; args[j-1] != 0; j++) {
	args[j] = args[j+3];
      }

      return 2;
    } else if (args[i][0] == '>') {
      free(args[i]);

      // Get the filename 
      if(args[i+1] != 0) {
	*output_filename = args[i+1];
      } else {
	return -1;
      }

      // Adjust the rest of the arguments in the array
      for(j = i; args[j-1] != 0; j++) {
	args[j] = args[j+2];
      }

      return 1;
    }
  }

  return 0;
}

// Expand the tilde symbol to mean the $HOME environment variable
void tilde (char ** args) {
  int ref_length;
  int home_length;
  int i;
  int j;
  int k;
  char * home;
  char * temp;

  // Calculate the total length we'll be needing for temp (our built-up location)
  for(i = 1; args[i] != 0; i++){
    if(args[i][0] == '~') {
       home = getenv ("HOME");

       // find out how long HOME variable is
       for (home_length = 0; home[home_length] != 0; home_length++);

       // find the length of the reference after the ~
       for (ref_length = 0; args[i][ref_length] != 0; ref_length++);

       // now build up the final location with the whole address
       temp = malloc (sizeof (char) * (home_length + ref_length));
       for (j = 0; j < home_length; j++) { temp[j] = home[j]; } // put home in temp
       for (j = 1; j < ref_length; j++) { temp[j + home_length - 1] = args[i][j]; } // put ref in temp

       args [i] = temp;
    }
  }
}

void add_to_bkg (pid_t child_id) {
  int i = 0;

  while (bkg_pid [i] != 0) {
    i++;
  }

  bkg_pid [i] = child_id;
}

void rm_from_bkg (int child_id) {

  // Find the id and remove it from the queue
  int i = 0;

  while (bkg_pid [i] != child_id) {
    i++;
  }

  bkg_pid [i] = 0;

  // Now shift everything to the right of the empty spot to the left
  while (i < (MAX_BKG - 1) && bkg_pid [i + 1] != 0) {
    bkg_pid [i] = bkg_pid [i + 1];
    bkg_pid [i + 1] = 0;
    i++;
  }
}

int num_pipes (char ** args) {
  int result = 0;
  int i = 0;

  for (i = 0; args[i] != 0; i++) {
    if (args[i][0] == '|') {
	result++;
    }
  }

  // Check to see if there's a command after the last pipe
  if (args [i - 1][0] == '|') {
    printf ("No command after last pipe...\n");
    result = 0;
  }

  return result;
}

int pipe_execvp (char ** args, int number_pipes) {
  int result = 0;
  int i;

  if (number_pipes == 0) { // if there is nothing to pipe, just run the command
    result = execvp (args [0], args);
    printf ("MYS [1][%d] -- result = %d\n", i++, result);	
  } else {
    result = recursive_execvp_1 (args, number_pipes);
    printf ("MYS [1][%d] -- result = %d\n", i++, result);	
  }

  return result;
}

// Recursively call commands, working backwards...
/* For example, if we have args "a|b|c|d", start with cmd d and recursively call everything
   before the last pipe to build up our result */
int recursive_execvp_2 (char ** cmd, char ** head_args, int number_pipes) {
  int result  = 0;
  int fd [2];

  printf ("got to recursive_execvp_2\n");
  pipe (fd);

  printf ("MYS -- number of pipes: %d\n", number_pipes);


  // hook up output
  dup2 (STDOUT_FILENO, fd [0]);

  printf ("MYS -- got here\n");
  if (number_pipes == 0) { // base case of resursive calls
    printf ("MYS -- base case of recursive call: %s\n", cmd [0]);
    close (fd [1]);

    result = execvp (cmd [0], cmd);
  } else {
    // hook up input
    dup2 (STDIN_FILENO, fd [1]);

    // call recursively to the next one down the chain

    printf ("MYS --- head(args) %s\n", head_args [0]);
    recursive_execvp_2 (last_command (cmd), head (head_args), number_pipes - 1);
  }

  return result;
}

// Takes a set of args and returns everything after the last pipe
char ** last_command (char ** args) {
  int i = 0;
  int pipe_location;

  while (args [i] != 0) {
    if (args [i][0] == '|') pipe_location = i;
    i++;
  }

  return (& args [pipe_location + 1]);
}

// Takes a set of args and returns everything before the last pipe
char ** head (char ** args) {
  int i = 0;
  int j;
  int pipe_location;

  while (args [i] != 0) {
    if (args [i][0] == '|') {
      pipe_location = i;
    }
    
    i++;
  }

  char * result [pipe_location];

  for (i = 0; i < pipe_location; i++) {
    printf ("got here 0\n");

    result[i][0] = & malloc (sizeof (char) * BUF_SIZE);

    // zero out the memory
    memset (result [i], 0, sizeof (char) * BUF_SIZE);
  }


  i = 0;
  while (i < pipe_location) {
    printf ("got here 0.0\n");
    (* result[i]) = malloc (sizeof (char) * BUF_SIZE);
    printf ("got here 1\n");

    if (args [i][0] == '|') {
      i++;
      continue;
    }

    for (j = 0; args [i][j] != 0; j++);

    printf ("got here 1\n");
    memcpy ((void *)result[0], (void *)args[i], j);
    i++;	
  }

  printf ("got here\n");

  for (i = 0; i < pipe_location; i++) {
    printf ("%s\n", args[i]);
  }

  printf ("got here\n");
  printf ("MYS [3][1] -- result [pipe_location] = %s\n", result [pipe_location - 1]);
  return result;
}

int recursive_execvp_1 (char ** args, int number_pipes) {
  int result = 0;
  int fd [2];
  char read_buffer [BUF_SIZE];
  char * last_cmd;
  char ** head_arg;
  int i = 0;

  pipe (fd);

  // SETUP
  if (close (fd [1]) == -1) perror ("close"); // Since we're the last command, don't need to redirect output
  if (dup2 (STDIN_FILENO, fd [0]) == -1) perror ("dup2");

  // RECURSIVE CALLING
  last_cmd = * (last_command (args));
  head_arg = head (args);
  
  printf ("MYS [2][%d] -- last_cmd = %s\n", i++, last_cmd);

  for (i = 0; head_arg [i] != 0; i++) {
    printf ("MYS [2][%d] -- head(args) = %s\n", i, head_arg[i]);
  }

  result = recursive_execvp_2 (& last_cmd, head (args), number_pipes - 1);
  
  printf ("MYS [2][%d] -- result = %d\n", i++, result);
  
  // EXECVP
  if (result != -1) { // only call if you haven't error'd yet
    printf ("MYS [2][%d] -- concatenated string = %s\n", i++, strcat (* args, (char *)read (fd [0], read_buffer, BUF_SIZE)));
    result = execvp (last_cmd, (char **) strcat (last_cmd, strcat (* args, (char *)read (fd [0], read_buffer, BUF_SIZE))));
    // the above line may cause problems...
  }

  printf ("MYS [2][%d] -- result = %d\n", i++, result);

  return result;
}

